简介
Google Protocol Buffer简称Protobuf,是由Google开发的用来序列化、反序列化数据结构的技术,它支持C++、Java、python等多种语言。
-
安装
下载源码:点击这里
安装:1
2
3
4./configure --prefix=$INSTALL_DIR
make
make check
make install
一个例子
使用Protobuf通常需要:
1、定义消息格式。
2、生成.h和.cc文件
3、使用Protobuf提供的API写代码
定义消息格式
消息格式定义文件一般以.proto后缀结尾,可以在.proto文件中添加想要序列化的消息。一个消息由类型和消息名组成。下面是addressbook.proto消息定义:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package tutorial;
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
message AddressBook {
repeated Person person = 1;
}
上面一package
开头,作用了命名空间类似,可以防止命名冲突。
消息定义有类型和消息名组成,类型包括bool
, int32
, float
, double
, string
。消息名后面的括号[]中default=
表示这个消息的默认值。
消息后面会后=1,=2
这样的字段,这是用来标记消息在二进制编码中的唯一性的。编号1-15编码占用空间小于1字节,为了优化考虑,经常用到的字段建议使用编号1-15。
每个消息类型前面还有个修饰符:required
:表示必须提供初始值,否则这个消息字段是未初始化的。optional
:表示如果没有提供初始值,那么可以指定默认值。repeated
:表示该消息字段可能会重复,出现多次或者0次。
生成.h和.cc文件
使用命令1
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
生成addressbook.pb.cc addressbook.pb.h文件
对应API
可以在addressbook.pb.h中拿出一部分代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38// required string name = 1;
inline bool has_name() const;
inline void clear_name();
static const int kNameFieldNumber = 1;
inline const ::std::string& name() const;
inline void set_name(const ::std::string& value);
inline void set_name(const char* value);
inline void set_name(const char* value, size_t size);
inline ::std::string* mutable_name();
inline ::std::string* release_name();
inline void set_allocated_name(::std::string* name);
// required int32 id = 2;
inline bool has_id() const;
inline void clear_id();
static const int kIdFieldNumber = 2;
inline ::google::protobuf::int32 id() const;
inline void set_id(::google::protobuf::int32 value);
// optional string email = 3;
inline bool has_email() const;
inline void clear_email();
static const int kEmailFieldNumber = 3;
inline const ::std::string& email() const;
inline void set_email(const ::std::string& value);
inline void set_email(const char* value);
inline void set_email(const char* value, size_t size);
inline ::std::string* mutable_email();
inline ::std::string* release_email();
inline void set_allocated_email(::std::string* email);
// repeated .tutorial.Person.PhoneNumber phone = 4;
inline int phone_size() const;
inline void clear_phone();
static const int kPhoneFieldNumber = 4;
inline const ::tutorial::Person_PhoneNumber& phone(int index) const;
inline ::tutorial::Person_PhoneNumber* mutable_phone(int index);
inline ::tutorial::Person_PhoneNumber* add_phone();
可以看出,对于required
和optional
字段,都有set_
和has_
方法,前者用来设置字段值,后者用来检验是否设置了值。所有字段都有clear_
方法,用来清除设置的值。
对于repeated
字段,有_size()
方法,用来检查有多少个这样的字段。
可以通过字段名()
的方法来获取字段的值,但要注意返回的是常引用。要想修改字段的是,通过mutable_字段名()
来获取指向字段的指针。
###序列化和解析
每个protobuf类都有API来序列化消息为二进制或解析二进制消息:bool SerializeToString(string* output) const;
: 把消息序列化为二进制的stringbool ParseFromString(const string& data);
: 解析二进制的string消息bool SerializeToOstream(ostream* output) const;
: 把消息序列化为二进制输出流。bool ParseFromIstream(istream* input);
: 从二进制输入流解析消息。
实战
二进制文件的读写
写消息,把消息序列化,写到二进制文件中,保存到硬盘。
writer.cpp把消息序列化写到硬盘,reader.cpp从硬盘读取消息并打印。注意编译时要链接protobuf库,使用选线-lprotobuf。
writer.cpp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using std::string;
int main()
{
tutorial::AddressBook address_book;
//添加一个用户信息
tutorial::Person* person = address_book.add_person();
person->set_name("xiaoming");
person->set_id(1);
tutorial::Person::PhoneNumber* phone_number = person->add_phone();
phone_number->set_number("13812345678");
phone_number->set_type(tutorial::Person::MOBILE);
//再添加一个用户
person = address_book.add_person();
person->set_name("xiaosan");
person->set_id(2);
person->set_email("xiaosan@email.com");
phone_number = person->add_phone();
phone_number->set_number("02012345678");
phone_number->set_type(tutorial::Person::HOME);
//保存为文本
{
std::fstream output("./addressbook.bin", std::ios::out | std::ios::binary | std::ios::trunc);
if (!address_book.SerializeToOstream(&output)) {
std::cerr << "Failed to parse address book." << std::endl;
return -1;
}
}
return 0;
}
reader.cpp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
int main()
{
tutorial::AddressBook address_book;
{
fstream input("./addressbook.bin", ios::in | ios::binary);
if(!address_book.ParseFromIstream(&input))
{
cerr<<"Failed to parse address book."<<endl;
return -1;
}
}
//输出address_book信息
for(int i = 0; i< address_book.person_size(); ++i)
{
const tutorial::Person& person = address_book.person(i);
cout<<"Person ID: "<<person.id()<<endl;
cout<<" Name: "<<person.name()<<endl;
if( person.has_email())
{
cout<<" E-mail address: "<<person.email()<<endl;
}
for( int j = 0; j < person.phone_size(); ++j)
{
const tutorial::Person::PhoneNumber& phone_number = person.phone(j);
switch(phone_number.type())
{
case tutorial::Person::MOBILE:
cout<<" Mobile phone #";
break;
case tutorial::Person::HOME:
cout<<" Home phone #";
break;
case tutorial::Person::WORK:
cout<<" Work phone #";
break;
}
cout<< phone_number.number()<<endl;
}
}
}
文本文件的读写
protobuf可以以文本形式保存到文本中,也可以从文中读取消息。配置文件可以以protobuf文本形式保存使用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53#include <iostream>
#include <fstream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>
#include "addressbook.pb.h"
using namespace std;
using google::protobuf::io::FileInputStream;
using google::protobuf::io::FileOutputStream;
using google::protobuf::Message;
//从文本读到protobuf中
bool ReadProtoFromTextFile(const char* filename, Message* proto) {
int fd = open(filename, O_RDONLY);
FileInputStream* input = new FileInputStream(fd);
bool success = google::protobuf::TextFormat::Parse(input, proto);
delete input;
close(fd);
return success;
}
//写到protobuf中
void WriteProtoToTextFile(const Message& proto, const char* filename) {
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
FileOutputStream* output = new FileOutputStream(fd);
google::protobuf::TextFormat::Print(proto, output);
delete output;
close(fd);
}
int main()
{
tutorial::AddressBook address_book;
{
fstream input("./addressbook.bin", ios::in | ios::binary);
if(!address_book.ParseFromIstream(&input))
{
cerr<<"Failed to parse address book."<<endl;
return -1;
}
}
//写到文本中
WriteProtoToTextFile(address_book, "./addressbook.txt");
return 0;
}